package com.example.retry.service.impl;

import java.time.Instant;
import java.time.LocalTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import com.example.retry.ex.DataRepairNeededException;
import com.example.retry.mock.MockStrategyEnum;
import com.example.retry.service.HelloService;

@Service
public class HelloServiceImpl implements HelloService {

	private static final Logger log = LoggerFactory.getLogger(HelloServiceImpl.class);
	private static ThreadLocal<Integer> countThreadLocal = ThreadLocal.withInitial(() -> 1);

	@Value("${mock.strategy.hello:EXCEPTION}")
	private String mockStrategyOfHello;

	@Autowired
	private TaskScheduler taskScheduler;

	@Override
	@Retryable(maxAttempts = 5, backoff = @Backoff(delay = 1000, multiplier = 2))
	public String sayHello(String name) {
		// 多次重试用的是同一个线程，并且线程在重试期间一直占用
		Optional<String> mockRet = MockStrategyEnum.of(mockStrategyOfHello).mock(t -> "echo:\t" + t, name,
				Collections.emptyList());
		return mockRet.orElse("echo:\t" + name);
	}
	
	@Recover
	@Override
	public String recover(Throwable e, String name) {
		// @Recover标注的方法要求:
		// 1.第一个参数是Throwable或者子类，能够接收@Retryable抛出的异常
		// 2.@Recover的返回值与@Retryable的返回值保持一致
		// 3.其余入参按照顺序接收@Retryable的入参
		log.error(e.getMessage(), e);
		throw new DataRepairNeededException("sayHelloAsync",name, e);
	}

	@Override
	@Retryable(maxAttempts = 5, backoff = @Backoff(delay = 1000, multiplier = 2))
	@Async("asyncExecutor")
	public String sayHelloAsync(String name) {
		// @Async针对的是整个方法，而不是单次重试
		// 多次重试用的同一个线程，并且线程在重试期间一直占用
		Optional<String> mockRet = MockStrategyEnum.of(mockStrategyOfHello).mock(t -> "echo:\t" + t, name,
				Collections.emptyList());
		return mockRet.orElse("echo:\t" + name);
	}

	@Override
	public void sayHelloSchedule(String name) {
		// taskScheduler可以指定一个线程池，无论本方法调用多少次，都用的是线程池里的线程
		long delay = 1000;
		long maxAttempts = 5;
		double multiplier = 2;
		taskScheduler.schedule(() -> sayHelloSchedule(name, delay, multiplier, maxAttempts),
				Instant.now().plusMillis(delay));
	}

	private void sayHelloSchedule(String name, long delay, double multiplier, long maxAttempts) {
		log.info("开始sayHelloSchedule方法第[{}]次---\t[{}]\t[{}]", countThreadLocal.get(), Thread.currentThread().getId(), LocalTime.now());
		countThreadLocal.set(countThreadLocal.get() + 1);
		try {
			Optional<String> mockRet = MockStrategyEnum.of(mockStrategyOfHello).mock(t -> "echo:\t" + t, name,
					Collections.emptyList());
			mockRet.orElse("echo:\t" + name);
		} catch (Exception e) {
			long attempts = maxAttempts - 1;
			if (attempts >= 1) {
				// taskScheduler可以指定一个线程池，无论这个方法调用多少次，都用的是线程池的线程
				long newDelay = Double.valueOf(delay * multiplier).longValue();
				taskScheduler.schedule(() -> sayHelloSchedule(name, newDelay, multiplier, attempts),
						Instant.now().plusMillis(newDelay));
			}
		}
	}

	@Override
	@Retryable(maxAttempts = 5, backoff = @Backoff(delay = 1000, multiplier = 2))
	public void batchAdd(List<String> list) {
		// 一样的list，下次重试时并不会减少list里面元素的个数
		list.iterator().remove();
		throw new RuntimeException("抛出异常。。。");
	}
	
	@Recover
	@Override
	public void recoverBatchAdd(Throwable e, List<String> list) {
		// @Recover标注的方法要求:
		// 1.第一个参数是Throwable或者子类，能够接收@Retryable抛出的异常
		// 2.@Recover的返回值与@Retryable的返回值保持一致
		// 3.其余入参按照顺序接收@Retryable的入参
		log.error(e.getMessage(), e);
		throw new RuntimeException(e);
	}

}
